/*
 * Copyright 2014-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package team.lourd.network.system.docs;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.hateoas.MediaTypes;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.hypermedia.LinkDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import team.lourd.network.system.own.domain.Record;
import team.lourd.network.system.own.domain.netel.NEModel;
import team.lourd.network.system.own.repository.jpa.RecordRepository;
import team.lourd.network.system.own.repository.jpa.netel.NEModelRepository;

import javax.servlet.RequestDispatcher;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration")
@AutoConfigureRestDocs
public class ApiDocumentation {

    @Autowired
    private NEModelRepository neModelRepository;

    @Autowired
    private RecordRepository recordRepository;

    @Rule
    public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;


    @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(this.restDocumentation)).build();
    }

    /**
     * @Description: 错误例子
     * @Param: []
     * @return: void
     * @Author: Lourd
     * @Date: 2019/1/22
     */
    @Test
    public void errorExample() throws Exception {
        this.mockMvc
                .perform(get("/error")
                        .requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 400)
                        .requestAttr(RequestDispatcher.ERROR_REQUEST_URI,
                                "/notes")
                        .requestAttr(RequestDispatcher.ERROR_MESSAGE,
                                "The tag 'http://localhost:8080/tags/123' does not exist"))
                .andDo(print()).andExpect(status().isBadRequest())
                .andExpect(jsonPath("error", is("Bad Request")))
                .andExpect(jsonPath("timestamp", is(notNullValue())))
                .andExpect(jsonPath("status", is(400)))
                .andExpect(jsonPath("path", is(notNullValue())))
                .andDo(document("error-example",
                        responseFields(
                                fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`(发生的HTTP错误)"),
                                fieldWithPath("message").description("A description of the cause of the error(错误原因的描述)"),
                                fieldWithPath("path").description("The path to which the request was made(请求路径)"),
                                fieldWithPath("status").description("The HTTP status code, e.g. `400`(HTTP 状态码)"),
                                fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred(发生错误的时间(以毫秒为单位))"))));
    }

    /**
     * @Description: 访问index的例子
     * @Param: []
     * @return: void
     * @Author: Lourd
     * @Date: 2019/1/22
     */
    @Test
    public void indexExample() throws Exception {
        List<String> list = Arrays.asList("permissions", "pMModels", "nEFlowCharts", "operations", "nEBranches", "nEModels", "users", "records", "records", "pMWorks", "roles", "nEDiagrams");
        List<LinkDescriptor> linkDescriptors = list.stream().map(t -> {
            return linkWithRel(t).description(String.format("The <<resources-%s,%s resource>>", t, upperCase(t)));
        }).collect(Collectors.toList());
        linkDescriptors.add(linkWithRel("profile").description("The ALPS profile for the service"));
        this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON))
                .andExpect(status().isOk())
                .andDo(document("index-example",
                        links(linkDescriptors),
                        responseFields(
                                subsectionWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));

    }

    @Test
    public void nEModelListExample() throws Exception {
        this.neModelRepository.deleteAll();

        // 创建测试模型
        for (int i = 0; i < 3; i++) {
            createNEModel("基本模型" + i,
                    String.format("基本模型%s的描述", i));
        }

        this.mockMvc.perform(get("/nEModels"))
                .andExpect(status().isOk())
                .andDo(document("nEModels-list-example",
                        links(
                                linkWithRel("self").description("Canonical link for this resource"),
                                linkWithRel("profile").description("The ALPS profile for this resource")),
                        responseFields(
                                subsectionWithPath("_embedded.nEModels").description("包含一组 数据(nEModel) <<resources-nEModels, NEModels resources>>"),
                                subsectionWithPath("_links").description("<<resources-tags-list-links, Links>> 链接其他资源(resouces)"),
                                subsectionWithPath("page").description("nEModels 的分页(page)信息"))));
    }

    @Test
    public void nEModelsCreateExample() throws Exception {
        Map<String, String> record = new HashMap<String, String>();
        record.put("description", "创建了一个基本模型");
        record.put("action", "创建操作");
        String recordLocation = this.mockMvc
                .perform(
                        post("/records").contentType(MediaTypes.HAL_JSON).content(
                                this.objectMapper.writeValueAsString(record)))
                .andExpect(status().isCreated()).andReturn().getResponse()
                .getHeader("Location");

        Map<String, Object> nEModel = new HashMap<String, Object>();
        nEModel.put("name", "基本模型");
        nEModel.put("description", "基本模型的描述");
        nEModel.put("records", Arrays.asList(recordLocation));

        this.mockMvc.perform(
                post("/nEModels").contentType(MediaTypes.HAL_JSON).content(
                        this.objectMapper.writeValueAsString(nEModel))).andExpect(
                status().isCreated())
                .andDo(document("nEModels-create-example",
                        requestFields(
                                fieldWithPath("name").description("此nEModel的名称"),
                                fieldWithPath("description").description("此nEModel的描述"),
                                fieldWithPath("records").description("一组 records 资源(resource)的 URIs"))));
    }

    @Test
    public void nEModelGetExample() throws Exception {
        Map<String, String> record = new HashMap<String, String>();
        record.put("description", "创建了一个基本模型");
        record.put("action", "创建操作");
        String recordLocation = this.mockMvc
                .perform(
                        post("/records").contentType(MediaTypes.HAL_JSON).content(
                                this.objectMapper.writeValueAsString(record)))
                .andExpect(status().isCreated()).andReturn().getResponse()
                .getHeader("Location");

        Map<String, Object> nEModel = new HashMap<String, Object>();
        nEModel.put("name", "基本模型");
        nEModel.put("description", "基本模型的描述");
        nEModel.put("records", Arrays.asList(recordLocation));

        String nEModelLocation = this.mockMvc
                .perform(
                        post("/nEModels").contentType(MediaTypes.HAL_JSON).content(
                                this.objectMapper.writeValueAsString(nEModel)))
                .andExpect(status().isCreated()).andReturn().getResponse()
                .getHeader("Location");

        this.mockMvc.perform(get(nEModelLocation))
                .andExpect(status().isOk())
                .andExpect(jsonPath("name", is(nEModel.get("name"))))
                .andExpect(jsonPath("description", is(nEModel.get("description"))))
                .andExpect(jsonPath("_links.self.href", is(nEModelLocation)))
                .andExpect(jsonPath("_links.records", is(notNullValue())))
                .andDo(print())
                .andDo(document("nEModel-get-example",
                        links(
                                linkWithRel("self").description("此规范链接 <<resources-nEModel,nEModel>>"),
                                linkWithRel("nEModel").description("此 <<resources-nEModel,nEModel>>"),
                                linkWithRel("records").description("此 nEModel的 records"),
                                linkWithRel("neBranches").description("此 nEModel的 neBranches")),
                        responseFields(
                                fieldWithPath("name").description("名称"),
                                fieldWithPath("description").description("描述"),
                                fieldWithPath("createdAt").description("创建时间"),
                                fieldWithPath("lastModifiedAt").description("修改时间"),
                                fieldWithPath("createdBy").description("创建人"),
                                fieldWithPath("lastModifiedBy").description("修改人"),
                                fieldWithPath("isOpen").description("是否允许公开"),
                                fieldWithPath("style").description("分类"),
                                fieldWithPath("type").description("类型"),
                                subsectionWithPath("_links").description("<<resources-nEModel-links,Links>> to other resources"))));
    }

    @Test
    public void nEModelUpdateExample() throws Exception {
        Map<String, Object> nEModel = new HashMap<String, Object>();
        nEModel.put("name", "基本模型");
        nEModel.put("description", "基本模型的描述");

        String nEModelLocation = this.mockMvc
                .perform(
                        post("/nEModels").contentType(MediaTypes.HAL_JSON).content(
                                this.objectMapper.writeValueAsString(nEModel)))
                .andExpect(status().isCreated()).andReturn().getResponse()
                .getHeader("Location");

        this.mockMvc.perform(get(nEModelLocation))
                .andExpect(status().isOk())
                .andExpect(jsonPath("name", is(nEModel.get("name"))))
                .andExpect(jsonPath("description", is(nEModel.get("description"))))
                .andExpect(jsonPath("_links.self.href", is(nEModelLocation)))
                .andExpect(jsonPath("_links.records", is(notNullValue())));

        Map<String, String> record = new HashMap<String, String>();
        record.put("description", "创建了一个基本模型");
        record.put("action", "创建操作");
        String recordLocation = this.mockMvc
                .perform(
                        post("/records").contentType(MediaTypes.HAL_JSON).content(
                                this.objectMapper.writeValueAsString(record)))
                .andExpect(status().isCreated()).andReturn().getResponse()
                .getHeader("Location");

        Map<String, Object> nEModelUpdate = new HashMap<String, Object>();
        nEModelUpdate.put("records", Arrays.asList(recordLocation));

        this.mockMvc.perform(
                patch(nEModelLocation).contentType(MediaTypes.HAL_JSON).content(
                        this.objectMapper.writeValueAsString(nEModelUpdate)))
                .andExpect(status().isNoContent())
                .andDo(document("nEModel-update-example",
                        requestFields(
                                fieldWithPath("name").description("名称").type(JsonFieldType.STRING).optional(),
                                fieldWithPath("description").description("描述").type(JsonFieldType.STRING).optional(),
                                fieldWithPath("records").description("一组 records 资源(resource)的 URIs").optional())));
    }


    public String upperCase(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    private void createNEModel(String name, String description) {
        NEModel nEModel = new NEModel();
        nEModel.setName(name);
        nEModel.setDescription(description);
        this.neModelRepository.save(nEModel);
    }

    private void createRecord(String description, String action) {
        Record record = new Record();
        record.setAction(action);
        record.setDescription(description);
        this.recordRepository.save(record);
    }
}
